﻿package projects.flashUpload {
	import flash.net.URLVariables;

	import extremefx.IDisposable;
	import extremefx.events.Events;
	import extremefx.events.ProgressEventArgs;

	import flash.events.DataEvent;
	import flash.events.Event;
	import flash.events.HTTPStatusEvent;
	import flash.events.IOErrorEvent;
	import flash.events.ProgressEvent;
	import flash.events.SecurityErrorEvent;
	import flash.net.FileReference;
	import flash.net.URLRequest;
	import flash.net.URLRequestMethod;
	import flash.utils.clearInterval;
	import flash.utils.clearTimeout;
	import flash.utils.getTimer;
	import flash.utils.setInterval;
	import flash.utils.setTimeout;

	public final class Upload implements IDisposable {
		public const OnStart:Events = Events.create(this);
		public const OnProgress:Events = Events.create(this);
		public const OnComplete:Events = Events.create(this);
		public const OnError:Events = Events.create(this);
		
		/**
		 * @author Marcelo Volmaro
		 */
		private var _fr:FileReference;//.name, .size, .type(ext on pc, type on mac)
		private var _assumeSuccessTimeout:uint = 30;
		private var _successTimeout:uint;
		private var _serverDataTimer:uint;
		private var _uploading:Boolean;
		private var _data:String;
		private var _lastError:String;
		private var _imAlive:uint;
		private var _time:Time;
		private var _progress: uint;

		public function Upload(pFileRef:FileReference){
			_fr = pFileRef;
			_time = new Time();
			_lastError = "";
		}

		public function get time():Time {
			return _time;
		}
		
		public function get size():uint {
			return _fr.size;
		}
		
		public function get name():String {
			return _fr.name;
		}
		
		public function get type():String {
			return _fr.type;
		}
		
		public function get data():String {
			return _data;
		}
		
		public function get lastError():String {
			return _lastError;
		}
		
		public function start(pUrl:String, pPostObject:Object = null):Boolean {
			if (_uploading)
				return false;
				
			_fr.addEventListener(Event.OPEN, _onOpen, false, 0, true);
			_fr.addEventListener(IOErrorEvent.IO_ERROR, _onIoError, false, 0, true);
			_fr.addEventListener(SecurityErrorEvent.SECURITY_ERROR, _onSecError, false, 0, true);
			_fr.addEventListener(HTTPStatusEvent.HTTP_STATUS, _onHttpStatus, false, 0, true);
			_fr.addEventListener(DataEvent.UPLOAD_COMPLETE_DATA, _onData, false, 0, true);
			_fr.addEventListener(Event.COMPLETE, _onComplete, false, 0, true);
			
			var request:URLRequest = new URLRequest(pUrl);
			request.method = URLRequestMethod.POST;
            var post:URLVariables = new URLVariables();
            
            for (var key:String in pPostObject) {
            	if (pPostObject.hasOwnProperty(key)) {
					post[key] = pPostObject[key];
				}
			}
			
			request.data = post;
			
			_fr.upload(request);//,"Filedata", false
			_uploading = true;
			return true;
		}

		public function stop():void {
			if (_uploading){
				_fr.cancel();
			}
		}
		
		private function _onTimeout(pError:Boolean = false):void {
			removeListeners();
			if (pError) Events.fire(OnError);
			Events.fire(OnComplete);
		}
	
		private function _onData(event:DataEvent):void {
			_data = event.data;
			_onTimeout(false);
		}
	
		private function _onComplete(event:Event):void {
			_serverDataTimer = setTimeout(_onTimeout, 100);
		}
		
		private function _onHttpStatus(event:HTTPStatusEvent):void {
			_lastError = "HttpStatus: "+event.status; 
			_onTimeout(true);
		}
	
		private function _onSecError(event:SecurityErrorEvent):void {
			_lastError = "SecurityError: "+event.text;
			_onTimeout(true);
		}
	
		private function _onIoError(event:IOErrorEvent):void {
			_lastError = "IOError: "+event.text;
			_onTimeout(true);		
		}
	
		private function _onProgress(event:ProgressEvent):void {
			var bytesLoaded:Number = event.bytesLoaded < 0 ? 0 : event.bytesLoaded;
			var bytesTotal:Number = event.bytesTotal < 0 ? 0 : event.bytesTotal;
			var now:uint = getTimer();
			
			if (!_progress || _progress < now - 100 || bytesLoaded == _fr.size){
				_progress = now;
				_time.update(bytesLoaded);
				
				// Because Flash never fires a complete event if the server doesn't respond after 30 seconds or on Macs if there
				// is no content in the response we'll set a timer and assume that the upload is successful after the defined amount of
				// time.  If the timeout is zero then we won't use the timer.
				if (bytesLoaded === bytesTotal && bytesTotal > 0 && _assumeSuccessTimeout > 0) {
					clearTimeout(_successTimeout);
					_successTimeout = setTimeout(_onTimeout, _assumeSuccessTimeout* 1000);
				}
				
				Events.fire(OnProgress, new ProgressEventArgs(bytesLoaded, bytesTotal));
			}
		}
		
		private function _onOpen(event:Event):void {
			Events.fire(OnStart);
			
			var i:uint = _progress = 0;
			
			_imAlive = setInterval(function():void{
				i++;
				if (i == 100) i = 0; 
			}, 500);
			
			_time.start(_fr.size);
		}
		
		private function removeListeners():void {
			//Tidy
			if (_imAlive) {
				clearInterval(_imAlive);
				_imAlive = 0;
			}
			
			if (_serverDataTimer) {
				clearTimeout(_serverDataTimer);
				_serverDataTimer = 0;
			}
			
			if (_successTimeout) {
				clearTimeout(_successTimeout);
				_successTimeout = 0;
			}
			
			
			if (_fr){
				_fr.removeEventListener(Event.OPEN, _onOpen);
				_fr.removeEventListener(ProgressEvent.PROGRESS, _onProgress);
				_fr.removeEventListener(IOErrorEvent.IO_ERROR, _onIoError);
				_fr.removeEventListener(SecurityErrorEvent.SECURITY_ERROR, _onSecError);
				_fr.removeEventListener(HTTPStatusEvent.HTTP_STATUS, _onHttpStatus);
				_fr.removeEventListener(Event.COMPLETE, _onComplete);
				_fr.removeEventListener(DataEvent.UPLOAD_COMPLETE_DATA, _onData);
			}
		}
		
		public function dispose():void {
			removeListeners();
			_fr = null;
			Events.removeAll(this);
		}
	}
}
